package spider

import (
	"asocrawler/conf"
	"asocrawler/db"
	"asocrawler/tool"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"runtime/debug"
	"strconv"
	"strings"
	"time"
)

const (
	kuaishouMainUrl = "https://www.kuaishou.com/?isHome=1"
	kuaishouApiUrl  = "https://www.kuaishou.com/graphql"
)

type KuaiShouSpider struct {
	ChannelName  string
	TimeInterval int
	Platform     string
}

func NewKuaiShouSpider() (v *KuaiShouSpider) {
	v = &KuaiShouSpider{
		ChannelName:  "快手",
		Platform:     "快手",
		TimeInterval: 2,
	}
	return
}

func (this KuaiShouSpider) Run() {
	go func() {
		this.run()
	}()
}
func (this *KuaiShouSpider) Println(v ...interface{}) {
	var str = fmt.Sprintf("[%s]-[%s]", this.ChannelName, this.Platform)
	if conf.Conf.Debug.Switch {
		str += "-[Debug]"
	}
	log.Println(str, fmt.Sprintln(v...))
}

func (this *KuaiShouSpider) getVideoList() (videoList []string, err error) {
	log.Println("正在打开快手首页...")
	wd, err := tool.OpenHeadLessWebDriver()
	if err != nil {
		log.Println("创建无头浏览器对象失败，err= ", err)
		return
	}
	//关闭一个webDriver会对应关闭一个chrome窗口
	//但是不会导致seleniumServer关闭
	defer func() {
		wd.Quit()             // 关闭浏览器
		tool.PutBrowserPool() // 放回池子
	}()

	if err = wd.Get(kuaishouMainUrl); err != nil {
		log.Println("打开快手首页失败!,err=", err)
		return
	}

	time.Sleep(3 * time.Second)

	// 设置设备信息
	_, err = wd.ExecuteScript("document.cookie='did=333;'", nil)
	if err != nil {
		return videoList, err
	}
	time.Sleep(1 * time.Second)

	// 再次打开
	if err = wd.Get(kuaishouMainUrl); err != nil {
		log.Println("打开快手首页失败!,err=", err)
		return
	}
	time.Sleep(1 * time.Second)

	tTimes := 0
	for {

		// 30秒未打开，强制退出
		if tTimes == 30 {
			err = errors.New("30秒未打开快手首页，强制退出")
			return
		}
		title, err := wd.Title()
		if err != nil {
			return videoList, err
		}
		if strings.Contains(title, "快手") {
			log.Println("打开首页成功!")
			break
		}
		time.Sleep(1 * time.Second)

		tTimes++
	}

	//time.Sleep(100 * time.Hour)
	time.Sleep(1 * time.Second)

	// 获取列表
	type videoItems struct {
		Id string `json:"id"`
	}
	var res []videoItems

	jsonInterface, err := wd.ExecuteScript(`return JSON.stringify(window.__APOLLO_STATE__.defaultClient["$ROOT_QUERY.visionHotRank({\"page\":\"home\"})"].items)`, nil)
	if err != nil {
		return
	}

	jsonStr, ok := jsonInterface.(string)
	if ok {

		err = json.Unmarshal([]byte(jsonStr), &res)
		if err != nil {
			return
		}

		// 如果是数组
		for _, v := range res {
			// 拆出标题  VisionHotRankItem:xxxx
			arr := strings.Split(v.Id, ":")
			if len(arr) > 1 {
				videoList = append(videoList, arr[1])
			}
		}
	}

	return
}

type KuaiShouwResult struct {
	Data struct {
		HotData struct {
			Feeds []struct {
				Author struct {
					HeaderUrl string `json:"headerUrl"` // 头像
					Id        string `json:"id"`        // 作者id
					Name      string `json:"name"`      // 作者名称
				} `json:"author"` // 作者信息
				Photo struct {
					Id            string `json:"id"`            // 视频id
					Caption       string `json:"caption"`       // 视频标题
					CoverUrl      string `json:"coverUrl"`      // 封面
					RealLikeCount int    `json:"realLikeCount"` // 点赞数
					Timestamp     int64  `json:"timestamp"`     // 发布时间 毫秒时间戳
					ViewCount     string `json:"viewCount"`     // 播放数
				} `json:"photo"`
			} `json:"feeds"`
		} `json:"hotData"`
	} `json:"data"`
}

func (this *KuaiShouSpider) collectVideoData(date string, videoList []string) (rs []db.NewRankVideoResult, err error) {
	client := &http.Client{}

	for _, title := range videoList {
		tryTimes := 0
		params := fmt.Sprintf(`{"operationName":"hotVideoQuery","variables":{"trendingId":"%s","page":"brilliant","webPageArea":"brilliantxxunknown"},"query":"fragment photoContent on PhotoEntity {\n  id\n  duration\n  caption\n  likeCount\n  viewCount\n  realLikeCount\n  coverUrl\n  photoUrl\n  photoH265Url\n  manifest\n  manifestH265\n  videoResource\n  coverUrls {\n    url\n    __typename\n  }\n  timestamp\n  expTag\n  animatedCoverUrl\n  distance\n  videoRatio\n  liked\n  stereoType\n  profileUserTopPhoto\n  __typename\n}\n\nquery hotVideoQuery($trendingId: String, $page: String, $webPageArea: String) {\n  hotData(trendingId: $trendingId, page: $page, webPageArea: $webPageArea) {\n    result\n    llsid\n    expTag\n    serverExpTag\n    pcursor\n    webPageArea\n    trendingId\n    feeds {\n      type\n      trendingId\n      author {\n        id\n        name\n        headerUrl\n        following\n        headerUrls {\n          url\n          __typename\n        }\n        __typename\n      }\n      photo {\n        ...photoContent\n        __typename\n      }\n      canAddComment\n      llsid\n      status\n      currentPcursor\n      __typename\n    }\n    __typename\n  }\n}\n"}`,
			title)

	re:
		req, err := http.NewRequest("POST", kuaishouApiUrl, strings.NewReader(params))
		if err != nil {
			return rs, err
		}

		req.Header.Set("content-type", "application/json")

		resp, err := client.Do(req)
		if err != nil {
			time.Sleep(1 * time.Second)
			return rs, err
		}

		defer resp.Body.Close()

		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return rs, err
		}

		var p KuaiShouwResult

		err = json.Unmarshal(b, &p)
		if err != nil {
			if tryTimes < 10 {
				this.Println("collectVideoData error,尝试重新请求...tryTimes：", tryTimes)
				tryTimes++
				time.Sleep(500 * time.Millisecond)
				goto re
			}
			err = errors.New("collectVideoData error ,data=" + string(b))
			return rs, err
		}

		// 每类热门是一系列相关视频，只取最热的第一条
		if len(p.Data.HotData.Feeds) > 0 {
			d := p.Data.HotData.Feeds[0]

			vPlatform := this.Platform
			videoId := d.Photo.Id + "-" + vPlatform
			publishTime := time.Unix(d.Photo.Timestamp/1000, 0).Format("2006-01-02 15:04:05")

			// 作者id
			userId := d.Author.Id + "-" + vPlatform

			// 视频源地址
			originUrl := fmt.Sprintf("https://www.kuaishou.com/short-video/%s", d.Photo.Id)

			// 播放量
			viewCount, err := strconv.Atoi(d.Photo.ViewCount)
			if err != nil {
				return rs, err
			}

			r := db.NewRankVideoResult{
				VideoId:       videoId,
				VideoTitle:    d.Photo.Caption,
				VideoPlatform: vPlatform,
				CoverUrl:      d.Photo.CoverUrl,
				PublishTime:   publishTime,
				OriginUrl:     originUrl,

				UserId:       userId,
				UserAvatar:   d.Author.HeaderUrl,
				UserNickName: d.Author.Name,

				//TotalShareCount:   d.Stat.Share,
				//TotalCommentCount: d.Stat.Reply,
				TotalDiggCount: d.Photo.RealLikeCount,
				TotalViewCount: viewCount,

				Date: date,
			}

			rs = append(rs, r)
		}
	}

	return
}

func (this *KuaiShouSpider) Collect(date string) (bool, error) {
	videoList, err := this.getVideoList()
	if err != nil {
		return false, err
	}

	rs, err := this.collectVideoData(date, videoList)
	if err != nil {
		return false, err
	}

	if len(rs) == 0 {
		this.Println("KuaiShouSpider data len = 0")
		tool.SendWarn("ASO报警-快手 爬虫报错", "ASO报警-newRank-快手 抖音爬虫报错,错误信息：无数据")
	} else {
		err := db.SaveNewRankData(rs)
		if err != nil {
			return false, err
		}
	}
	return true, nil

}

func (this *KuaiShouSpider) run() {
	defer func() {
		if err := recover(); err != nil {
			errMsg := string(debug.Stack())
			this.Println("[崩溃]err=", err, errMsg)
		}
	}()
	if conf.Conf.Debug.Switch {
		for _, fDate := range conf.Conf.Debug.DateList {
			this.Println("日期:", fDate, "开始采集数据...")
			succ, err := this.Collect(fDate)
			if succ {
				this.Println("日期:", fDate, "采集数据完成")
			} else {
				if err == nil {
					this.Println("日期:", fDate, "数据未更新")
				} else {
					this.Println("日期:", fDate, "出现错误err:", err)
				}
			}
		}
	} else {
	re:
		fDate := time.Now().Format("2006-01-02")
		this.GetDataByDate(fDate)
		time.Sleep(time.Duration(this.TimeInterval) * time.Hour)
		goto re
	}
}

func (this *KuaiShouSpider) GetDataByDate(date string) {
	this.Println("日期:", date, "开始采集数据...")
	success, err := this.Collect(date)
	if err != nil {
		tool.SendWarn("ASO报警-快手 爬虫报错", "ASO报警-快手 爬虫报错,错误信息："+err.Error())
		this.Println("日期:", date, "采集失败 err=", err)
	} else {
		if success {
			this.Println("日期:", date, "采集数据完成 稍后再次采集...")
		} else {
			this.Println("日期:", date, "数据未更新 稍后重新采集...")
		}
	}
}
